#include "cv_face_detector.hpp"

#include "spdlog/spdlog.h"

Detection::Detection(cv::Rect2i face, std::vector<cv::Rect2i> eyes,
                     std::vector<cv::Rect2i> noses)
    : face_(face), eyes_(eyes), noses_(noses) {
  SPDLOG_DEBUG("face center point {}, {}", face.x + face.width / 2,
               face.y + face.height / 2);
}

std::size_t CVFaceDetector::DetectFace(const cv::Mat& frame,
                                       std::vector<cv::Rect2i>& rects) {
  rects.resize(0);
  face_cascader_.detectMultiScale(
      frame, rects, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
  SPDLOG_WARN("Faces : {}", rects.size());
  return rects.size();
}

std::size_t CVFaceDetector::DetectNose(const cv::Mat& frame,
                                       std::vector<cv::Rect2i>& rects) {
  rects.resize(0);
  if (model_ & component::DetectModel::kMODEL_NOSE)
    nose_cascader_.detectMultiScale(
        frame, rects, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
  SPDLOG_WARN("Noses : {}", rects.size());
  return rects.size();
}

std::size_t CVFaceDetector::DetectEyes(const cv::Mat& frame,
                                       std::vector<cv::Rect2i>& rects) {
  if (model_ & component::DetectModel::kMODEL_EYE)
    eye_cascader_.detectMultiScale(
        frame, rects, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
  SPDLOG_WARN("Eyes : {}", rects.size());
  return rects.size();
}

CVFaceDetector::CVFaceDetector() : model_(component::DetectModel::kMODEL_FACE) {
  SPDLOG_TRACE("Constructed.");
}

CVFaceDetector::CVFaceDetector(const std::string& face_param,
                               const std::string& eye_param,
                               const std::string& nose_param, uint8_t model) {
  ConfigModel(model);
  LoadParams(face_param, eye_param, nose_param);
  SPDLOG_TRACE("Constructed.");
}

CVFaceDetector::~CVFaceDetector() {}

Expr CVFaceDetector::LoadParams(const std::string& face_param,
                                const std::string& eye_param,
                                const std::string& nose_param) {
  Expr err = Error::kOK;
  if (model_ & component::DetectModel::kMODEL_FACE) {
    if (!face_cascader_.load(face_param)) {
      err = Error::kFILE_ERROR;
      SPDLOG_ERROR("{} : {}", err.What(), face_param);
      return err;
    } else if (face_cascader_.empty()) {
      err = Error::kOTHER_ERROR;
      SPDLOG_ERROR("{} : {}", err.What(), face_param);
      return err;
    }
  }
  if (model_ & component::DetectModel::kMODEL_EYE) {
    if (!eye_cascader_.load(eye_param)) {
      err = Error::kFILE_ERROR;
      SPDLOG_ERROR("{} : {}", err.What(), eye_param);
      return err;
    } else if (eye_cascader_.empty()) {
      err = Error::kOTHER_ERROR;
      SPDLOG_ERROR("{} : {}", err.What(), eye_param);
      return err;
    }
  }
  if (model_ & component::DetectModel::kMODEL_NOSE) {
    if (!nose_cascader_.load(nose_param)) {
      err = Error::kFILE_ERROR;
      SPDLOG_ERROR("{} : {}", err.What(), nose_param);
      return err;
    } else if (nose_cascader_.empty()) {
      err = Error::kOTHER_ERROR;
      SPDLOG_ERROR("{} : {}", err.What(), nose_param);
      return err;
    }
  }
  return err;
}

void CVFaceDetector::ConfigModel(uint8_t model) {
  model_ = model;

  auto fun = [&](std::string orin, std::string name, bool option) {
    std::string judge = option ? "ON" : "OFF";
    return orin + name + " : " + judge + "\n";
  };

  std::string str = "\n";
  bool nose = model_ & component::DetectModel::kMODEL_NOSE;
  bool eye = model_ & component::DetectModel::kMODEL_EYE;
  bool face = model_ & component::DetectModel::kMODEL_FACE;
  str = fun(str, "Face", face);
  str = fun(str, "Eye", eye);
  str = fun(str, "Nose", nose);

  SPDLOG_WARN("{}", str);
}

Expr CVFaceDetector::Detect(const cv::Mat& frame, double scale) {
  faces_.clear();
  eyes_.clear();

  std::vector<cv::Rect2i> eyes, noses, faces;
  Expr err;
  cv::Mat result, hsv, dst;
  std::vector<cv::Mat> channels;

  if (frame.empty()) {
    err = Error::kFRAME_ERROR;
    SPDLOG_ERROR("{}", err.What());
    return err;
  }

  {
    // cv::GaussianBlur(frame, frame, cv::Size(5, 5), 0);
    cv::cvtColor(frame, hsv, cv::COLOR_BGR2HSV);
    cv::split(hsv, channels);
    cv::inRange(channels[0], 6, 13, dst);
    cv::threshold(dst, dst, 140, 255, cv::THRESH_BINARY);
    cv::imshow("window", dst);
    cv::cvtColor(frame, result, cv::COLOR_BGR2GRAY);
    double fx = 1 / scale;
    if (fx != 1.)
      cv::resize(result, result, cv::Size(), fx, fx, cv::INTER_LINEAR_EXACT);
  }

#if 0
  if (DetectFace(result, faces)) {
    faces_ = faces;
    for (auto& face : faces) {
      cv::Mat roi = frame(face);
      std::vector<cv::Rect2i> eye_rects, nose_rects;

      if (DetectEyes(roi, eyes))
        for (auto& eye : eyes) {
          eye_rects.emplace_back(cv::Rect2i(face.tl() + eye.tl(), eye.size()));
        }

      if (DetectNose(roi, noses))
        for (auto& nose : noses) {
          eye_rects.emplace_back(
              cv::Rect2i(face.tl() + nose.tl(), nose.size()));
        }

      Detection detection(face, eye_rects, nose_rects);
      detections_.emplace_back(detection);
    }
  }
#else
  DetectEyes(frame, eyes);
  if (eyes.size() > 2) {
    for (auto iti = eyes.begin(); iti < eyes.end(); iti++)
      for (auto itj = iti + 1; itj < eyes.end(); itj++) {
        auto center_diff =
            component::AutoCenter(*iti) - component::AutoCenter(*itj);

        auto dis_x = std::abs(center_diff.x);
        auto aver_dis_x = (iti->width + itj->width) / 2;
        if (dis_x > 3 * aver_dis_x) continue;
        if (dis_x < aver_dis_x) continue;

        auto dis_y = std::abs(center_diff.y);
        if (dis_y > 0.7 * std::min(iti->height, itj->height)) continue;
        Face face(*iti, *itj);
        face.LightJudge(dst);
        faces_.emplace_back(face);
      }
  }
#endif
  err = Error::kOK;
  return err;
}

void CVFaceDetector::VisualizeResult(const cv::Mat& output, bool add_lable) {
#if 0
  if (detections_.size() != 0) {
    for (auto& detection : detections_) {
      component::Draw(output, detection.face_, component::kGREEN, add_lable);

      if (model_ & component::DetectModel::kMODEL_EYE)
        if (detection.eyes_.size() != 0)
          for (auto& eye : detection.eyes_) {
            component::Draw(output, eye, component::kBLUE, add_lable);
          }

      if (model_ & component::DetectModel::kMODEL_NOSE)
        if (detection.noses_.size() != 0)
          for (auto& nose : detection.noses_)
            component::Draw(output, nose, component::kRED, add_lable);
    }
  }
#endif
  for (auto& eye : eyes_)
    component::Draw(output, eye, painting::kBLUE, add_lable);
  for (auto& face : faces_) face.Draw(output, painting::kYELLOW, true);
  component::DrawInfo(output, std::to_string(faces_.size()), 1,
                      painting::kGREEN);
}
